<template>
  <div
    ref="container"
    class="grid-layout-design"
    :class="{
      'report-mode': currentDashboard.type === '报告',
    }"
    :style="{
      minHeight: `${dataStore.layoutData.height}px`,
    }"
    @dragover="onDragOver"
    @drop.stop="onDrop"
    @click="onLayoutContentClick"
  >
    <!-- grid-layout 封装 -->
    <widgets-layout ref="widgetLayout" :layout="rootLayout" />
    <!-- 吸附参考线 -->
    <template v-if="draggingDomRect && draggingItem">
      <div
        v-if="alignmentFlagHT"
        class="line ht"
        :style="{
          top: `${ht}px`,
          ...htStyle,
        }"
      ></div>
      <div
        v-if="alignmentFlagHC"
        class="line hc"
        :style="{
          top: `${hc}px`,
          ...hcStyle,
        }"
      ></div>
      <div
        v-if="alignmentFlagHB"
        class="line hb"
        :style="{
          top: `${hb}px`,
          ...hbStyle,
        }"
      ></div>
      <div
        v-if="alignmentFlagVL"
        class="line vl"
        :style="{
          left: `${vl}px`,
          ...vlStyle,
        }"
      ></div>
      <div
        v-if="alignmentFlagVC"
        class="line vc"
        :style="{
          left: `${vc}px`,
          ...vcStyle,
        }"
      ></div>
      <div
        v-if="alignmentFlagVR"
        class="line vr"
        :style="{
          left: `${vr}px`,
          ...vrStyle,
        }"
      ></div>
    </template>
    <!-- 报告模式删除按钮 -->
    <template
      v-if="currentDashboard.type === '报告' && pdfReportDelIconList.length > 1"
    >
      <a-icon-font
        v-for="l in pdfReportDelIconList"
        :key="`btn-del-${l.index}`"
        :style="{ top: l.top + 'px' }"
        class="btn-del"
        :class="{ 'lock-mode': isLock }"
        type="iconicon-beifen5"
        @click="() => deletePdfPage(l.index)"
      />
    </template>
  </div>
</template>
<script lang="ts">
import {
  getDashboardByProjectId,
  saveDashboard,
  updateDashboard,
} from '@/api/dashboard'
import { cloneDeep, isArray, isString, debounce } from 'lodash'
import { themeColorSet } from '@/util/util'
import {
  getWidgetData,
  saveWidget,
  updateWidget,
  widgetQueryByProject,
  deleteWidgetBatch,
  widgetCopied,
} from '@/api/widget'
import {
  buildSaveParameters,
  checkIsFilterForm,
  filterGraphData,
  mergeFilterFormFilters,
  checkIsVistabs,
  handleInteractionActionObject,
} from '@/util/common'

import {
  chartTitleMap,
  // filterTypeName,
  filterFormTypeName,
  vistabsTypeName,
  GeoMapDefaultConfig,
  GEOMAP_NAME,
  // tableTypeName,
  CONFIGURATION_NAME,
  chartDetailToVisClassMaps,
  GRAPH_ARRAY,
  isAggrStatus,
  GIS_VIEW,
} from '@/config/contant'
import VisualizationStore from '@/store/modules/visualization'
import { Component, Vue, Watch, Prop } from 'vue-property-decorator'
import WidgetsLayout from './widgets-layout.vue'
import { getDetailDefaultOptionsByChartType } from '@/components/common/VisualizationComp/default-chart-options' // 默认配置
import {
  ColumnInterface,
  componentTypeEnum,
  ControlConfigurationInterface,
  controlTypeEnum,
  GridItemDataInterface,
  gridItemFilterInterface,
  KeyValueStore,
} from '../vueGridLayout/interfate/grid-Item-data-inteface'
import { getDefaultValueFromDashBoardData } from './controller-setting'
import {
  parseChartOptionToFunction,
  parseGetDataResult,
  getWidgetGraphData,
} from '../common/VisualizationDashboard/util'
import { enableConvertToCoor } from '@/util/recommend-visualization'
import { DomReact } from './util'
import { IKeyValueStore } from '@/store/modules/dataview'
import visGraphService from '@/store/modules/vis-graph-service'
import UserStore from '@/store/modules/user'

const defaultChartoptions = {
  titleIsShow: true,
  // titleHeight: 44,
  titleTextMaxLength: 30,
  titleFontSize: 16,
  titleFontColor: '#373B52',
  xAxisIsShow: true,
  yAxisIsShow: true,
  maxBarWidth: 28,
  sizeRatio: 0.5, // 针对热力图的形状大小设置
  // legendPosition: 'right',
}

// const touchGridTypeName = 'touch-grid-item'

@Component({
  components: {
    WidgetsLayout,
  },
})
export default class GridLayoutDesign extends Vue {
  //  停止高度更新的标志
  @Prop({ type: Boolean, default: false })
  holdHeight!: boolean

  @Prop({ type: Boolean, default: false }) private isLock!: boolean

  private dropTargetId: string = 'root' // 组件拖动释放时识别的目标组件编号，若无层级关系为root，有层级关系为对应组件的id编号

  private pdfReportDelIconList: Array<{ index: number; top: number }> = [] // 报告模式的删除按钮list

  alignmentFlagHT: boolean = false // 检测到有组件横向头部对齐标识
  alignmentFlagHC: boolean = false // 检测到有组件横向中部对齐标识
  alignmentFlagHB: boolean = false // 检测到有组件横向底部对齐标识
  alignmentFlagVL: boolean = false // 检测到有组件纵向左边对齐标识
  alignmentFlagVC: boolean = false // 检测到有组件纵向中部对齐标识
  alignmentFlagVR: boolean = false // 检测到有组件纵向右侧对齐标识
  ht: number = -1 // 横向头部参考线偏移量
  htStyle: IKeyValueStore = {} // 头部参考线样式
  hc: number = -1 // 横向中部参考线偏移量
  hcStyle: IKeyValueStore = {} // 横向中部参考线样式
  hb: number = -1 // 横向底部参考线偏移量
  hbStyle: IKeyValueStore = {} // 横向底部参考线样式
  vl: number = -1 // 纵向左侧参考线偏移量
  vlStyle: IKeyValueStore = {} // 纵向左侧参考线样式
  vc: number = -1 // 纵向中部参考线偏移量
  vcStyle: IKeyValueStore = {} // 纵向中部参考线样式
  vr: number = -1 // 纵向右侧参考线偏移量
  vrStyle: IKeyValueStore = {} // 纵向右侧参考线样式
  draggingDomRect: DomReact | null = null

  // filterTypeName: string = filterTypeName
  filterFormTypeName: string = filterFormTypeName
  // tableTypeName: string = tableTypeName
  CONFIGURATION_NAME = CONFIGURATION_NAME

  // touchGridTypeName: string = touchGridTypeName

  //  第一次请求dashboard tabs  信息
  firstTimeQueryTabs: boolean = true
  private hasTextChart: any = false

  public get isVisitor() {
    //  访客身份
    return UserStore.isProjectGuest
  }

  //  需要插入到文本的公式
  public get insertFormula() {
    return VisualizationStore.getInsertFormula
  }
  //  删除的公式 - 需要文本里更新
  public get deleteFormula() {
    return VisualizationStore.getDeleteFormula
  }
  //  更新的公式 - 需要文本里更新
  public get updateFormula() {
    return VisualizationStore.getUpdateFormula
  }
  //  公式列表
  public get formulaList() {
    return VisualizationStore.getFormulaList
  }
  //  主题配置
  private get themeSetting() {
    return VisualizationStore.layoutData.themeSetting
  }

  get configurationExecuteId() {
    return this.dataStore.configurationExecuteId
  }

  get loadingMaps() {
    return this.dataStore.loadingMaps
  }

  get filterFormExecuteId() {
    return this.dataStore.filterFormExecuteId
  }

  private get rootLayout() {
    return this.dataStore.gridList.filter((item) => {
      return (
        item.chartOptions.parentId === undefined ||
        item.chartOptions.parentId === 'root'
      )
    })
  }

  private get draggingFlag() {
    return this.dataStore.draggingFlag
  }

  private get draggingItem() {
    return this.dataStore.draggingItem
  }
  /**
   * 监听formulaList 变化 来更新文本中的公式
   */
  @Watch('formulaList', { deep: true })
  handleFormulaListChange(newValue: any) {
    if (newValue) {
      this.$nextTick(() => {
        //  check 是否失效，是否结果有误 'UPDATED_FAILED', 'DELETED'
        newValue.forEach((list: any) => {
          list.formulaList.forEach((formula: any) => {
            if (['UPDATED_FAILED', 'DELETED'].includes(formula.status)) {
              this.modifyFormulaInText(formula, 'delete')
            } else {
              //  根据状态。更新result值
              this.modifyFormulaInText(formula, 'update')
            }
          })
        })
      })
    }
  }

  @Watch('insertFormula', { deep: true })
  public onInsertFormula(newValue: any) {
    if (newValue.formula) {
      this.detectInsertContent(newValue)
    }
  }

  @Watch('deleteFormula', { deep: true })
  public onDeleteFormula(newValue: any) {
    if (newValue.formula) {
      this.modifyFormulaInText(newValue, 'delete')
      this.saveAllTextValue()
    }
  }

  @Watch('updateFormula', { deep: true })
  public onUpdateFormula(newValue: any) {
    if (newValue.formula) {
      this.modifyFormulaInText(newValue, 'update')
      this.saveAllTextValue()
    }
  }

  /** 当前激活状态下的filter-chart的关联图表 */
  get currentFilterFormAsCharts(): Array<string | number> {
    return this.dataStore.currentFilterFormAsCharts || []
  }
  /**
   * 修改文本中的公式
   * @param newValue 公式信息
   * @param flag update / delete 公式的类型
   */
  modifyFormulaInText(newValue: any, flag: string) {
    this.$nextTick(() => {
      setTimeout(() => {
        const elements = document.querySelectorAll('.mceNonEditable')
        let formula: any = ''
        let result: any = ''
        // eslint-disable-next-line no-restricted-syntax
        for (const item of elements) {
          const id = item.getAttribute('data-id')
          //  获取旧的formula, result 为后续replace 作准备
          if (newValue.id === Number(id)) {
            //  可能拿的是新插入的formula, result
            formula = item.getAttribute('data-formula')
            result = item.getAttribute('data-result')
            if (formula !== newValue.formula) {
              //  防止取到新公式的情况
              break
            }
          }
        }

        if (formula.length > 0) {
          //  对每个编辑器内部内容作替换
          this.getTinymce().editors.forEach((editor: any) => {
            let content = cloneDeep(editor.getContent())
            content = content.replaceAll(
              `<a class="mceNonEditable formula-style" data-id="${newValue.id}" data-result="${result}" data-formula="${formula}">${formula}</a>`,
              flag === 'delete'
                ? `<a class="mceNonEditable error-style" data-id="${newValue.id}">ERROR</a>`
                : `<a class="mceNonEditable formula-style" data-id="${newValue.id}" data-result="${newValue.result}" data-formula="${newValue.formula}">${newValue.formula}</a>`
            )
            content = content.replaceAll(
              `<a class="mceNonEditable result-style" data-id="${newValue.id}" data-result="${result}" data-formula="${formula}">${result}</a>`,
              flag === 'delete'
                ? `<a class="mceNonEditable error-style" data-id="${newValue.id}">ERROR</a>`
                : `<a class="mceNonEditable result-style" data-id="${newValue.id}" data-result="${newValue.result}" data-formula="${newValue.formula}">${newValue.result}</a>`
            )
            if (flag === 'update') {
              content = content.replaceAll(
                `<a class="mceNonEditable error-style" data-id="${newValue.id}">ERROR</a>`,
                `<a class="mceNonEditable result-style" data-id="${newValue.id}" data-result="${newValue.result}" data-formula="${newValue.formula}">${newValue.result}</a>`
              )
            }
            editor.setContent(content)
          })
        }
      }, 1000)
    })
  }

  private getGlobal = (): any => window

  private getTinymce = () => {
    const global = this.getGlobal()

    return global && global.tinymce ? global.tinymce : null
  }
  /**
   * 插入公式
   */
  public detectInsertContent(item: any) {
    this.getTinymce().activeEditor.execCommand(
      'mceInsertContent',
      false,
      ` <span class="formula-wrapper"><a class='formula-style mceNonEditable'
            data-id=${item.id}
            data-result=${item.result}
            data-formula='${item.formula}'
        >${item.formula}</a></span> `
    )
  }

  /**
   * 保存文本内容
   */
  private async saveTextValue(gridItem: any) {
    //  Save text value by update Widget
    gridItem.chartOptions.value = this.transferTextValue(
      gridItem.chartOptions.value
    )
    const parameters = buildSaveParameters(gridItem)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }
    if (gridItem.widgetId) {
      await updateWidget(parameters) // 保存text widget
    }
  }
  /**
   * 保存所有文本内容
   */
  private saveAllTextValue() {
    this.$nextTick(() => {
      setTimeout(() => {
        this.gridList.forEach((item: any) => {
          if (item.chartType === 'text') {
            this.saveTextValue(item)
          }
        })
      }, 1000)
    })
  }

  get dataStore() {
    return VisualizationStore
  }

  get gridList(): Array<GridItemDataInterface> {
    return VisualizationStore.gridList
  }

  get unitX() {
    const containerWidth = VisualizationStore.layoutData.width
    const margin = this.dataStore.girdMargin
    const cols = this.dataStore.gridColNum

    return (containerWidth - margin[0] * (cols + 1)) / cols
  }

  get unitY() {
    return this.dataStore.gridRowHeight
  }

  get projectId() {
    return (this.$route.params as any).id || localStorage.getItem('projectId')
  }
  /**
   * 根据主题配置，设置主题色变量
   */
  @Watch('themeSetting', { deep: true })
  private handleThemeConfig(newValue: any) {
    if (newValue) {
      const bodyStyles = document.body.style
      const result: any = themeColorSet(newValue.theme, newValue)

      if (result) {
        // eslint-disable-next-line no-restricted-syntax
        for (const key in result) {
          if (key) {
            bodyStyles.setProperty(key, result[key])
          }
        }
      }
    }
  }

  //  设置主题配置为默认值
  private defaultThemeConfig() {
    this.dataStore.setThemeConfig({
      theme: 'light',
      panelBg: {
        type: 'default', //  default, pure
        color: '#F3F3F7',
      },
      componentBg: {
        type: 'default', //  default, pure
        color: 'rgb(255,255,255)',
      },
    })
  }

  private heightUpdated = debounce(
    (value: any) => this._heightUpdated(value),
    300
  )
  /**
   * 报告模式 - 高度变化，自动添加分隔符/删除icon 以及保存
   */
  public _heightUpdated(newValue: any) {
    const { height } = newValue
    const heightNumber = Number(height.slice(0, height.indexOf('px')))
    if (height && this.currentDashboard.type === '报告' && !this.holdHeight) {
      if ((heightNumber - 864) % 860 !== 0) {
        this.autoFillSplit()

        setTimeout(() => {
          this.onSave()
        }, 100)
      }
      this.autoFillDeleteIcon(heightNumber)
    }

    this.$emit('layout-height', heightNumber)
    this.layoutHeight = heightNumber
  }

  //  layout 真实高度
  private layoutHeight: number = 0

  /**
   * 自动放置上/下分隔符
   * @param type 新增一页 / 新建报告
   * @param time 需要放置的页数
   * @returns
   */
  public autoFillSplit(type: string = 'add', time?: number) {
    if (type === 'add') {
      this.onHeightChange(this.layoutHeight + 842 + 20) //  20 for gap width
    }
    //  add upSplit
    this.autoDropSplit('upSplit', time)
    //  add downSplit
    this.autoDropSplit('downSplit', time)
  }

  /**
   * 填充pdf报告的删除按钮
   */
  private autoFillDeleteIcon(heightNumber: number) {
    const pageHeight = 842
    const gapHeight = 20
    const offsetY = 36 // y方向上的偏移
    const times = Math.floor(heightNumber / pageHeight)
    const list = []
    for (let i = 0; i < times; i += 1) {
      list.push({
        index: i,
        top: i * (pageHeight + gapHeight) + offsetY,
      })
    }
    this.pdfReportDelIconList = list
  }

  /**
   * 删除pdf报告某一页
   * @param index: 报告页码，从0开始
   */
  private deletePdfPage(index: number) {
    if (this.isLock) {
      return
    }
    const hideReminder =
      Number(localStorage.getItem('hidePdfPageDeleteReminder')) || false
    if (hideReminder) {
      this.onDeletePdfPage(index)
    } else {
      const h = this.$createElement
      this.$confirm({
        title: '确定要删除该页吗？',
        content: h('div', {}, [
          h('p', '页面中的组件会一起删除'),
          h(
            'a-checkbox',
            { class: 'report-page-delete-check' },
            '下次不再提示'
          ),
        ]),
        okText: '确定',
        cancelText: '取消',
        onOk: () => {
          this.onHidePdfDeleteReminder()
          this.onDeletePdfPage(index)
        },
      })
    }
  }

  //  调整画布高度
  public onHeightChange(value: number): void {
    //  仅在报告模式使用，不存在最小高度的校验
    VisualizationStore.setBgHeightWithoutLmt(value)
  }

  // report模式下，当删除报告的某一页时，弹出确认框，如果勾选了“下次不再提示”，则下次不再弹出确认框
  public onHidePdfDeleteReminder(): void {
    const target = document.querySelector(
      '.report-page-delete-check .ant-checkbox'
    )
    const disableConfirm = target?.classList.contains('ant-checkbox-checked')
    if (disableConfirm) {
      localStorage.setItem(
        'hidePdfPageDeleteReminder',
        String(disableConfirm ? 1 : 0)
      )
    }
  }

  // report模式下，删除报告某一页
  public onDeletePdfPage(index: number): void {
    const list = cloneDeep(this.sortLayoutItemsByRowCol(this.gridList))
    const upSplitList: Array<GridItemDataInterface> = list.filter(
      (l) => l.chartType === 'upSplit'
    )
    const downSplitList: Array<GridItemDataInterface> = list.filter(
      (l) => l.chartType === 'downSplit'
    )
    const upLimitY = upSplitList[index].y // 根据页码获取当前上限位置
    const downLimitY = downSplitList[index].y // 根据页码获取当前下限位置
    const pageY = Math.round(
      (842 + 20) / (this.unitY + VisualizationStore.girdMargin[1])
    ) // 当前页后续组件在y方向上需要移动的值

    // 保留下来的组件
    const remainingList = list.filter((gridItem) => {
      // 剔除要删除的组件
      return !(gridItem.y >= upLimitY && gridItem.y <= downLimitY)
    })
    remainingList.forEach((gridItem) => {
      // 更改后续页面中组件的y值
      if (gridItem.y >= upLimitY) {
        gridItem.y -= pageY
      }
    })

    const deleteList = list.filter((gridItem: any) => {
      // 要删除的组件
      return gridItem.y >= upLimitY && gridItem.y <= downLimitY
    })
    const widgetIds = deleteList?.map((gridItem) => gridItem.widgetId)

    VisualizationStore.setGridList(remainingList)
    this.onHeightChange(this.layoutHeight - 842 - 20) //  20 for gap width

    deleteWidgetBatch({
      data: {
        projectId: this.projectId,
        widgetIds,
      },
    }).then(() => {
      this.$message.success('删除成功')
      this.onSave()
    })
  }

  mounted() {
    localStorage.setItem('projectId', `${(this.$route.params as any).id || ''}`)
    this.dataStore.setCurrentGridItem(null)
    this.dataStore.setGridList([])
    this.dataStore.setCurrentDashboardData({}) // 当前没有dashboard
    this.dataStore.setActiveSettingItem('background')
    this.defaultThemeConfig()
    this.initEventBus()
    // 由于pipeline编辑后表名称会有变化，所以初始化之前先查询对应数据信息
    this.init(null)
    this.initConfigurationWS()
    ;(window as any).dataStore = this.dataStore
  }

  initEventBus() {
    this.dataStore.eventBus.$on('aschartchange', this.onAschartchange)
    this.dataStore.eventBus.$on('widget-item-move', this.itemMoving)
    this.dataStore.eventBus.$on('widget-item-moved', this.itemMoveEnd)
    this.dataStore.eventBus.$on('sub-layout-updated', this.layoutUpdatedEvent)
    this.dataStore.eventBus.$on('widget-on-save', this.onSave)
    this.dataStore.eventBus.$on('widget-drag-move', this.onGridItemDragMove)
    this.dataStore.eventBus.$on('widget-resize-move', this.onGridItemResizeMove)
    this.dataStore.eventBus.$on('widget-height-updated', this.heightUpdated)
    this.dataStore.eventBus.$on('widget-on-copy', this.onCopy)
    this.dataStore.eventBus.$on('theme-change', this.onSave)
    this.dataStore.eventBus.$on('panel-color-change', this.onSave)
  }

  destroyEventBus() {
    this.dataStore.eventBus.$off('aschartchange', this.onAschartchange)
    this.dataStore.eventBus.$off('widget-item-move', this.itemMoving)
    this.dataStore.eventBus.$off('widget-item-moved', this.itemMoveEnd)
    this.dataStore.eventBus.$off('sub-layout-updated', this.layoutUpdatedEvent)
    this.dataStore.eventBus.$off('widget-on-save', this.onSave)
    this.dataStore.eventBus.$off('widget-drag-move', this.onGridItemDragMove)
    this.dataStore.eventBus.$off(
      'widget-resize-move',
      this.onGridItemResizeMove
    )
    this.dataStore.eventBus.$off('widget-height-updated', this.heightUpdated)
    this.dataStore.eventBus.$off('widget-on-copy', this.onCopy)
    this.dataStore.eventBus.$off('theme-change', this.onSave)
    this.dataStore.eventBus.$off('panel-color-change', this.onSave)
  }

  init(tabId: any) {
    if (!tabId) {
      VisualizationStore.setActiveSettingItem('background')
    }
    VisualizationStore.setLoadingStatus(true)
    //  init dashboard width/height/background color; Case: switch different projects in one session
    VisualizationStore.setBackgroundWidth(1180)
    getDashboardByProjectId({
      data: {
        projectId: this.projectId,
      },
    })
      .then(async (response) => {
        VisualizationStore.setLoadingStatus(false)
        if (
          response.data.code === 100 &&
          response.data.result &&
          response.data.result[0].id
        ) {
          //  设置所有画布tab 信息
          VisualizationStore.setSysTabs(response.data.result)
          let result = response.data.result[0]
          if (tabId) {
            // 撤回刷新页面，保持当前tab
            result = this.sysTabs.find((item: any) => item.id === tabId)
          }
          VisualizationStore.setCurrentDashboardData(result)
          await this.resetDashboardSetting(result)
          // if (tabId) {
          //   // 拿到新数据渲染之后让组件失焦，放置撤回后update了撤回前的信息
          //   VisualizationStore.setActiveSettingItem('background')
          // }
          this.firstTimeQueryTabs = false
        }
      })
      .catch(() => {
        VisualizationStore.setLoadingStatus(false)
        //  mute add sys dashboard
        this.dataStore.eventBus.$emit('add-panel-dashboard', 'panel')
        this.firstTimeQueryTabs = false
      })
  }
  /**
   * 对layout 中item 根据x, y 进行排序
   */
  sortLayoutItemsByRowCol(layout: Array<any>): Array<any> {
    return layout.sort((a, b) => {
      if (a.y > b.y || (a.y === b.y && a.x > b.x)) {
        return 1
      }
      return -1
    })
  }

  private get currentDashboard() {
    return VisualizationStore?.currentDashboard
  }

  private get currentTabId() {
    return VisualizationStore?.currentDashboard?.id ?? 0
  }

  @Watch('currentTabId', { deep: true })
  private handleDashboardChange(newValue: any) {
    //  当前dashboard 有更新并且不是第一次请求的时候
    if (newValue && !this.firstTimeQueryTabs) {
      this.resetDashboardSetting(VisualizationStore.currentDashboard)
    }
  }

  //  更改tab时，重置dashboard信息
  public async resetDashboardSetting(result: any) {
    this.dashBoardId = result.id
    const layout = result.layout ? JSON.parse(result.layout) : {}
    if (layout.w && layout.h) {
      VisualizationStore.setBackgroundWidth(Number.parseFloat(layout.w))
      VisualizationStore.setBackgroundHeight(Number.parseFloat(layout.h))
      // VisualizationStore.setBackgroundColor(layout.background)
    }
    if (layout.themeSetting) {
      VisualizationStore.setThemeType(layout.themeSetting.theme)
      VisualizationStore.setPanelBg(layout.themeSetting.panelBg)
      VisualizationStore.setComponentBg(layout.themeSetting.componentBg)
    }
    if (Array.isArray(layout.coordinations)) {
      VisualizationStore.setCoordination(layout.coordinations)
    }
    layout.gridItems = layout.gridItems || []

    const { pipelineId } = result
    this.dataStore.setPipelineId(pipelineId)

    const widgetList: any = await this.initAllWidgetList()
    setTimeout(() => {
      if (layout.gridItems && Array.isArray(layout.gridItems)) {
        const ids: Array<number> = []
        layout.gridItems.forEach((gridItem: KeyValueStore, index: number) => {
          if (
            ![
              CONFIGURATION_NAME,
              vistabsTypeName,
              'text',
              'upSplit',
              'downSplit',
            ].includes(gridItem.chartType)
          ) {
            gridItem.chartType = gridItem.chartType.split
              ? gridItem.chartType.split('|')
              : gridItem.chartType
          }
          // 处理graph的情况
          if (gridItem.config.length !== 0) {
            const config = JSON.parse(gridItem.config)
            if (gridItem.chartType === 'configuration') {
              // 控件组件要获取新的pipeline下面的参数配置的值
              // 同时需要更新childId 的情况 - info
              const target = widgetList
                .filter(
                  (item: { tid: number; widgets: any[] }) =>
                    item.tid === config.chartOptions.dataId
                )[0]
                ?.widgets.filter(
                  (item: KeyValueStore) => item.type === 'configuration'
                )[0]
              if (target && target.data.params) {
                const newValue = target.data.params[0]?.formData
                config.chartOptions.value = cloneDeep(newValue)
              }
              if (target && target.data.info) {
                config.chartOptions.info = target.data.info
              }
            }

            gridItem.dataQuery = {
              data: {
                id: config.chartOptions.dataId,
                type: config.chartOptions.dataType,
                widgetJson: config.widgetJson,
              },
            }

            if (
              config?.interactionJson?.relateWidgets &&
              config?.interactionJson?.relateWidgets.length > 0
            ) {
              gridItem.interactionJson = config.interactionJson
            }
            //  add themeSetting in chartOptions
            gridItem.chartOptions = {
              ...config.chartOptions,
              ...handleInteractionActionObject(config.chartOptions),
              themeSetting: this.themeSetting,
            }
            // 筛选器的数据字段是startNode，未统一成dataId，为了保证复制项目后dataId的统一，在dataId存在且两者不一致时赋值给startNode
            const { dataId, startNode } = gridItem.chartOptions
            if (
              gridItem.chartType.includes('filterForm') &&
              dataId &&
              startNode !== dataId
            ) {
              gridItem.chartOptions.startNode = dataId
            }
          }
          if (gridItem.widgetId) {
            ids.push(gridItem.widgetId)
          } else if (!gridItem.chartOptions) {
            const item = layout.gridItems[index]
            layout.gridItems[index].chartOptions = {
              showChart: false,
              value: [],
              size: [
                Math.floor(
                  this.unitX * item.w +
                    Math.max(0, item.w - 1) * this.dataStore.girdMargin[0]
                ),
                Math.floor(
                  this.unitY * item.h +
                    Math.max(0, item.h - 1) * this.dataStore.girdMargin[1]
                ),
              ],
            }
          }
        })
        const gridItems = (layout.gridItems as Array<any>) || []
        if (
          layout.filters &&
          isArray(layout.filters) &&
          layout.filters.length > 0
        ) {
          ;(layout.filters as Array<KeyValueStore>).forEach((filterItem) => {
            const fieldConfig: Array<KeyValueStore> = []
            filterItem.fieldConfig.forEach((fieldInfo: KeyValueStore) => {
              const column: ColumnInterface = {
                tableId: filterItem.dataId,
                label: fieldInfo.column,
                value: fieldInfo.column,
                type: fieldInfo.columnType,
                isDate: controlTypeEnum.timePicker === fieldInfo.controlType,
                isNumber: false, // 暂时不支持数值型
              }
              fieldConfig.push({
                ...fieldInfo,
                column,
                componentId:
                  (fieldInfo.associationChart as Array<string>)[0] || '',
                value: getDefaultValueFromDashBoardData(
                  fieldInfo.controlRender,
                  fieldInfo
                ), // fieldInfo.defaultValue
              })
            })
            gridItems.push({
              ...filterItem,
              fieldConfig,
            })
          })
        }
        if (
          layout.tables &&
          isArray(layout.tables) &&
          layout.tables.length > 0
        ) {
          gridItems.push(...layout.tables)
        }

        const resultItems = this.setStaticAttr(gridItems)

        VisualizationStore.setGridList(
          this.sortLayoutItemsByRowCol(resultItems)
        )
        this.initWidget()
      }
    }, 100)
  }

  //  为分隔符 add static attr when pdf mode
  private setStaticAttr(items: any) {
    if (this.currentDashboard.type === '报告') {
      items.forEach((item: any) => {
        item.static = ['upSplit', 'downSplit'].includes(item.chartType)
      })
    }
    return items
  }

  /**
   * 初始化wiget
   */
  initWidget() {
    this.gridList.forEach((gridItem: GridItemDataInterface, index: number) => {
      if (
        (gridItem.chartType && gridItem.chartType === CONFIGURATION_NAME) ||
        gridItem.chartType === 'upSplit' ||
        gridItem.chartType === 'downSplit'
      ) {
        return
      }

      if (checkIsFilterForm(gridItem)) {
        return
      }

      if (checkIsVistabs(gridItem)) {
        return
      }

      parseChartOptionToFunction(gridItem.chartOptions, gridItem.chartType)
      const ignoreGetDataChartList = [GIS_VIEW] // 不需要触发 getData接口的chart
      if (
        (gridItem.widgetId ||
          (gridItem.chartType === 'table' &&
            gridItem.chartOptions.dataId &&
            isArray(gridItem.chartOptions.columns) &&
            gridItem.chartOptions.columns.length > 0)) &&
        gridItem.chartOptions &&
        !ignoreGetDataChartList.some((key) => gridItem.chartType.includes(key))
      ) {
        this.getWidgetData(
          gridItem.chartOptions as KeyValueStore,
          gridItem,
          index
        )
      }
    })

    this.$nextTick(() => {
      //  更新最新的结果 不需要每次都获取公式列表；当获取完text data 之后去获取，然后更新
      if (this.hasTextChart) {
        VisualizationStore.fetchFormulaList({
          projectId: Number(this.$route.params.id),
        })
      }
    })
  }

  /**
   * 获取筛选参数
   */
  getFilterFormData(dataId: string, componentId: string): KeyValueStore | null {
    const gridFilters = this.dataStore.gridList.filter((gridItem) => {
      return gridItem.chartType === 'filter' && gridItem.dataId === dataId
    })
    if (gridFilters.length > 0) {
      const result: KeyValueStore = {}
      gridFilters.forEach((item) => {
        item.fieldConfig.forEach((fieldInfo: ControlConfigurationInterface) => {
          if (fieldInfo.associationChart.includes(componentId)) {
            result[fieldInfo.column.value] = {
              compType: fieldInfo.controlRender,
              columnType: fieldInfo.columnType,
              value: fieldInfo.value,
            }
          }
        })
      })
      return result
    }
    return null
  }
  getFilterItem(
    fieldName: string,
    value: any,
    controlType: string,
    columnType?: string
  ): KeyValueStore | null {
    let result: any = null
    switch (controlType) {
      case componentTypeEnum.radio:
      case componentTypeEnum.select:
        if (isString(value) && value !== '') {
          result = {
            col: fieldName,
            filterType: '=',
            values: [],
            checkedList: [value],
          }
        }
        break

      case componentTypeEnum.checkbox:
        if (value && isArray(value) && value.length > 0) {
          result = {
            col: fieldName,
            type: columnType,
            filterType: 'in',
            values: [],
            checkedList: value,
          }
        }
        break

      case componentTypeEnum.rangeDay:
        if (
          value &&
          isArray(value) &&
          value.length === 2 &&
          value[0] &&
          value[1]
        ) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp', // timestamp | datetime
            values: [value[0], value[1]],
            format: 'yyyy-MM-dd',
            checkedList: [value[0], value[1]],
          }
        }
        break
      case componentTypeEnum.rangeMonth:
        if (
          value &&
          isArray(value) &&
          value.length === 2 &&
          value[0] &&
          value[1]
        ) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp', // timestamp | datetime
            values: [value[0], value[1]],
            format: 'yyyy-MM',
            checkedList: [value[0], value[1]],
          }
        }

        break
      case componentTypeEnum.rangeYear:
        if (
          value &&
          isArray(value) &&
          value.length === 2 &&
          value[0] &&
          value[1]
        ) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp', // timestamp | datetime
            values: [value[0], value[1]],
            format: 'yyyy',
            checkedList: [value[0], value[1]],
          }
        }
        break
      default:
        break
    }

    return result
  }

  getTextData(gridItem: GridItemDataInterface) {
    if (gridItem && gridItem.chartOptions && gridItem.chartType === 'text') {
      const dataSend: KeyValueStore = {
        data: {
          id: gridItem.widgetId,
          type: gridItem.chartType, //  'text'
          text_data: 'dashboard_data',
        },
      }
      this.$set(gridItem, 'isLoading', true)
      getWidgetData(dataSend)
        .then((response: any) => {
          if (response.data.code === 100) {
            this.$set(
              gridItem,
              'chartOptions',
              response.data.result.extraData.chartOptions
            )
          }
          this.$set(gridItem, 'isLoading', false)
        })
        .catch(() => {
          this.$set(gridItem, 'isLoading', false)
        })
    } else {
      console.error('call error: getTableData must be used by table')
    }
  }

  getTableData(
    gridItem: GridItemDataInterface,
    withFilterSend?: boolean,
    onlyLocal: boolean = true
  ) {
    if (gridItem && gridItem.chartOptions && gridItem.chartType === 'table') {
      /** 表格组件有点特殊，自带分页组件，所以在关联状态时要做二次确认 */
      const withFilter =
        this.currentFilterFormAsCharts.includes(gridItem.widgetId) ||
        withFilterSend
      const filterFormJson =
        withFilter && gridItem.filterFormJson ? gridItem.filterFormJson : []
      const dataSend: KeyValueStore = {
        data: {
          id: gridItem.chartOptions.dataId,
          type: gridItem.chartOptions.dataType,
          tableJson: {
            curPage: withFilter ? 1 : gridItem.chartOptions.curPage,
            name: '_record_id_',
            pageSize: gridItem.chartOptions.pageSize,
            sortCol: gridItem.chartOptions.sortCol, //  sortCol; interact info in -> filters
            filters: withFilter
              ? mergeFilterFormFilters(
                  gridItem.chartOptions.filter || [],
                  filterFormJson
                )
              : gridItem.chartOptions.filter,
          },
        },
      }
      this.$set(gridItem, 'isLoading', true)

      getWidgetData(dataSend)
        .then((response: any) => {
          if (response.data.code === 418) {
            this.$message.error(response.data.tips)
          }
          if (response.data.code === 1106) {
            this.errorFilterWidgets.push(gridItem.i)
            this.errorMsgMap[gridItem.i] = response.data.message
          }
          if ([100, 1106].includes(response.data.code)) {
            const gridItemClone = cloneDeep(gridItem)
            const { chartOptions } = gridItemClone
            const index = this.getGridIndex(gridItem.i as string, 'i')
            const gridItemSet = {
              ...gridItemClone,
              isLoading: false,
              chartOptions: {
                ...chartOptions,
                curPage: response.data.result?.page?.curPage || 0,
                totalElements: response.data.result?.page?.totalElements || 0,
                pageSize: response.data.result?.page?.pageSize || 50,
                value: response.data.result
                  ? parseGetDataResult(
                      response.data.result,
                      undefined,
                      gridItem
                    )
                  : [],
                columns:
                  chartOptions.columns ||
                  response.data.result?.columns.map(
                    (column: { name: string }) => column.name
                  ),
              },
            }
            VisualizationStore.updateWidget({
              gridItem: gridItemSet,
              index,
              onlyLocal,
            })
          } else {
            this.$set(gridItem, 'isLoading', false)
          }
          if (withFilter) {
            setTimeout(() => {
              this.releaseLoadingTag(gridItem.widgetId)
            }, 100)
          }
        })
        .catch(() => {
          this.$set(gridItem, 'isLoading', false)
          if (withFilter) {
            setTimeout(() => {
              this.releaseLoadingTag(gridItem.widgetId)
            }, 100)
          }
        })
    } else {
      this.releaseLoadingTag(gridItem.widgetId)
      console.error('call error: getTableData must be used by table')
    }
  }

  /**
   * 根据 gridItem 获取graph widget 数据
   */
  private async getGraphData(
    gridItem: GridItemDataInterface,
    withFilter?: boolean
  ) {
    this.$set(gridItem, 'isLoading', true)
    const callback = withFilter
      ? (data: any) => filterGraphData(gridItem.filterFormJson || [], data)
      : undefined
    const value = await getWidgetGraphData(gridItem, this.projectId, callback)
    /** 将数据缓存到graphService中，以便筛选组件使用 */
    // visGraphService.save({id: gridItem.chartOptions?.dataId || '', data: value})
    const { nodePosition = [] } = gridItem.chartOptions
    value.nodes.forEach((node: any) => {
      const targetPosition = (
        nodePosition as Array<{
          id: string
          x: number
          y: number
        }>
      ).filter((p) => p.id === node.id)[0]
      if (targetPosition) {
        node.x = targetPosition.x
        node.y = targetPosition.y
      }
    })
    this.$set(gridItem.chartOptions, 'value', value)
    // this.$set(gridItem, 'chartType', chartTypeOld)
    this.$set(gridItem, 'showChart', true)
    this.$set(gridItem, 'isLoading', false)
    setTimeout(() => {
      this.releaseLoadingTag(gridItem.widgetId)
    }, 100)
  }

  /** 用于存储code为1106的response的widget */
  public errorFilterWidgets = [] as Array<any>
  /** 错误消息 */
  public errorMsgMap = {} as any

  /**
   * 获取widget数据
   */
  getWidgetData(
    dataSetting: KeyValueStore,
    gridItem: GridItemDataInterface,
    index: number,
    isAutoSave?: boolean,
    withFilter?: boolean,
    keepCurrent?: boolean
  ) {
    const filters: KeyValueStore | null = this.getFilterFormData(
      dataSetting.dataId,
      gridItem.i as string
    )
    const filterSend = [] as Array<any>
    if (filters) {
      Object.keys(filters).forEach((fieldName: string) => {
        const controlRender = filters[fieldName].compType
        const value = filters[fieldName].value as any
        if (
          (value &&
            !['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) &&
            value[0] !== '') ||
          (value &&
            ['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) &&
            value[0] &&
            value[1])
        ) {
          const filterItem = this.getFilterItem(
            fieldName,
            value,
            controlRender,
            filters[fieldName].columnType
          )
          if (filterItem) {
            filterSend.push(filterItem)
          }
        }
      })
    }
    const filterFormJson = gridItem.filterFormJson || []

    if (
      gridItem.chartType[0] === 'upSplit' ||
      gridItem.chartType[0] === 'downSplit'
    ) {
      return
    }

    if (gridItem.chartType === 'table') {
      this.getTableData(gridItem, withFilter)
      return
    }
    if (gridItem.chartType === 'text') {
      this.hasTextChart = true
      this.getTextData(gridItem)
      return
    }
    if (
      GRAPH_ARRAY.includes(
        Array.isArray(gridItem.chartType)
          ? gridItem.chartType[1]
          : gridItem.chartType
      )
    ) {
      this.getGraphData(gridItem, withFilter)
      return
    }

    const query = {
      data: gridItem.dataQuery?.data,
    }
    ;(<any>query.data.widgetJson.config.filters) = filterSend
    /*
    gridItem.chartOptions = {
      ...gridItem.chartOptions,
      ...{
        labelIsShow: false,
      },
    }
     */
    if (enableConvertToCoor(gridItem.chartOptions)) {
      // 需要映射地理数据（省份、城市、ip等映射成经纬度）
      query.data.convert = gridItem.chartOptions.geoTransform.fields
    }

    /** 筛选数据 */
    if (withFilter && query.data?.widgetJson?.config) {
      query.data.widgetJson.config.filters = mergeFilterFormFilters(
        query.data.widgetJson.config.filters || [],
        filterFormJson
      )
    }

    this.$set(gridItem, 'isLoading', true)
    this.errorFilterWidgets = this.errorFilterWidgets.filter(
      (item) => item !== gridItem.i
    )
    getWidgetData(query)
      .then((response) => {
        if (response.data.code === 1106) {
          this.errorFilterWidgets.push(gridItem.i)
          this.errorMsgMap[gridItem.i] = response.data.message
        }
        /** 在筛选器模式下有可能筛选不出数据，状态为1106 */
        if ([100, 1106].includes(response.data.code)) {
          setTimeout(() => {
            this.$set(VisualizationStore.gridList[index], 'isLoading', false)
          }, 100)

          const chartType = Array.isArray(gridItem.chartType)
            ? gridItem.chartType[1]
            : gridItem.chartType
          if (isAutoSave) {
            const parameters = buildSaveParameters(gridItem)
            // parameters.data.tid = gridItem.dataId
            // parameters.data.type = 'task'
            if (!parameters.projectId) {
              parameters.projectId = this.projectId
            }
            if (this.dashBoardId) {
              parameters.data.dashboardId = this.dashBoardId
            }

            saveWidget(parameters).then((responsesave) => {
              if (responsesave.data.code === 100) {
                this.$set(gridItem, 'widgetId', responsesave.data.result)
                // this.$set(gridItem.formData, 'dataId',  gridItem.dataId)
                // 处理数据
                const dataResult =
                  parseGetDataResult(
                    response.data?.result || {
                      columns: [],
                      data: [],
                      extraData: {},
                    },
                    chartType,
                    gridItem
                  ) || []
                // if (query.data.widgetJson !== undefined) {
                //   dataResult = restructGetDataResult(
                //     dataResult,
                //     query.data.widgetJson
                //   )
                // }

                VisualizationStore.gridItemSyncBindData({
                  data: {
                    query,
                    data: dataResult,
                    columns: gridItem.columns,
                    formData: gridItem.chartOptions,
                  },
                  gridItem,
                  index,
                  withFilter,
                  noChangeCurrent: !!keepCurrent,
                })
                this.onSave()
              }
            })
          } else {
            const dataResult = parseGetDataResult(
              response.data?.result || { columns: [], data: [], extraData: {} },
              chartType,
              gridItem
            )
            // if (query.data.widgetJson !== undefined) {
            //   dataResult = restructGetDataResult(
            //     dataResult,
            //     query.data.widgetJson
            //   )
            // }
            VisualizationStore.gridItemSyncBindData({
              data: {
                query,
                data: dataResult,
                columns: gridItem.columns,
                formData: gridItem.chartOptions,
              },
              gridItem,
              index,
              withFilter,
              noChangeCurrent: !!keepCurrent,
            })
          }
        } else {
          this.$set(gridItem, 'isLoading', false)
        }
        if (withFilter) {
          setTimeout(() => {
            this.releaseLoadingTag(gridItem.widgetId)
          }, 100)
        }
      })
      .catch((error) => {
        console.log('error', error)
        this.$set(gridItem, 'isLoading', false)
        if (withFilter) {
          setTimeout(() => {
            this.releaseLoadingTag(gridItem.widgetId)
          }, 100)
        }
      })
  }

  /** 释放加载标识 */
  releaseLoadingTag(widgetId: string | number) {
    this.loadingMaps[`widget_${this.filterFormExecuteId}`] =
      this.loadingMaps[`widget_${this.filterFormExecuteId}`] || []
    this.$set(
      this.loadingMaps,
      `widget_${this.filterFormExecuteId}`,
      this.loadingMaps[`widget_${this.filterFormExecuteId}`].filter(
        (id: any) => id !== widgetId
      )
    )

    setTimeout(() => {
      if (this.loadingMaps[`widget_${this.filterFormExecuteId}`].length === 0) {
        VisualizationStore.setIsSettingPanelChangeLock(false)
      }
    }, 1000)
  }
  //  filter 配置 关联chart 变化
  onAschartchange(data: any) {
    const newValueSet = new Set(data?.newValue || [])
    /** 解决table在取消勾选后仍然无法重置的情况 */
    VisualizationStore.setCurrentFilterFormAsCharts(data?.newValue || [])
    const diff = (data.old || []).filter((item: any) => !newValueSet.has(item))
    diff.forEach((id: string | number) => {
      let index = -1
      const find = this.dataStore.gridList.find(
        (grid: GridItemDataInterface, i: number) => {
          index = i
          return grid.widgetId === id
        }
      )
      if (find) {
        this.getWidgetData(find.chartOptions, find, index, false, false, true)
      }
    })
  }

  onLayoutContentClick(event: any) {
    const $element = event.target as HTMLElement
    if ($element && $element.className && $element.className.includes) {
      const classNameArray = $element.className
      if (
        classNameArray.includes('grid-layout-design') ||
        classNameArray.includes('vue-grid-layout')
      ) {
        VisualizationStore.setActiveSettingItem('background')
        if (this.currentDashboard.type === '报告') {
          // 报告模式没有画布配置
          VisualizationStore.setSettingPanelIsOpen(false)
        }
      }
    }
    event.stopPropagation()
  }
  //  获取默认名
  getDefaultName(chartType?: string) {
    let chartTypeUsed: string = ''

    if (chartType) {
      if (isArray(chartType)) {
        ;[, chartTypeUsed] = chartType
      } else if (/\|/.test(chartType)) {
        ;[, chartTypeUsed] = chartType.split('|')
      } else {
        chartTypeUsed = chartType
      }
    }

    let result = ''
    const nameAll = VisualizationStore.gridList.reduce(
      (rec: Array<string>, item: any) => {
        if (item.formData) {
          rec.push(item.formData.title || '')
        }
        if (item.chartOptions && item.chartOptions.title) {
          rec.push(item.chartOptions.title)
        }
        return rec
      },
      []
    )

    const prefix =
      chartTypeUsed && chartTitleMap[chartTypeUsed]
        ? chartTitleMap[chartTypeUsed]
        : '组件'

    for (let i = 1; i < 5000; i += 1) {
      const name = `${prefix}${i}`
      if (!nameAll.includes(name)) {
        result = name
        break
      }
    }
    return result
  }

  onDragOver(event: any) {
    event.preventDefault()
  }
  /**
   * 自动放置分隔符
   * @param chartInfo - upSplit, downSplit
   * @param time - 放置的次数
   */
  public async autoDropSplit(chartInfo: any, time?: number) {
    const times = time || Math.floor(VisualizationStore.layoutData.height / 842)
    const configuration: any = {
      chartOptions: {
        chartType: chartInfo,
      },
      chartType: chartInfo,
      x: 16, //
      y: chartInfo === 'upSplit' ? 43 * (times - 1) : 37.5 + 43 * (times - 1), //
      w: 74,
      h: chartInfo === 'downSplit' ? 5.2 : 4.2,
      i: `split${Math.random().toString(36).slice(-8)}`,
      resizable: false,
      moved: false,
      widgetType: 'system',
      static: true,
    }
    if (
      this.gridList.some(
        (gridItem) =>
          gridItem.chartType === chartInfo && gridItem.y === configuration.y
      )
    ) {
      // 避免重复添加分隔符，不然删除的时候会有索引的问题
      return
    }
    const parameters = buildSaveParameters(configuration)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }
    const response: any = await saveWidget(parameters) // 保存configuration widget
    configuration.widgetId = response.data.result

    VisualizationStore.addGridItem(configuration)
  }

  private async onDropText(event: any) {
    const compGridPos = this.calcXY({
      offsetLeft: event.clientX,
      offsetTop: event.clientY,
      dragOffset: this.dataStore.draggingOffset,
    })
    const compSize = this.calcWH(320, 80)
    const compMinSize = this.calcWH(50, 50)

    const title = this.getDefaultName('text')
    const itemId = `text${Math.random().toString(36).slice(-8)}`
    const configuration: GridItemDataInterface = {
      chartOptions: {
        chartType: 'text',
        parentId: this.dropTargetId,
        title,
        value: '',
        showBorder: true,
        showTitle: true,
      },
      chartType: 'text',
      x: compGridPos.x,
      y: compGridPos.y,
      w: compSize.w,
      h: compSize.h,
      i: itemId,
      minW: compMinSize.w,
      minH: compMinSize.h,
      resizable: true,
      moved: true,
      widgetType: 'system',
    }

    const parameters = buildSaveParameters(configuration)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }
    const response: any = await saveWidget(parameters) // 保存configuration widget
    configuration.widgetId = response.data.result

    VisualizationStore.addGridItem(configuration)

    setTimeout(() => {
      VisualizationStore.setActiveSettingItem('text')

      VisualizationStore.setCurrentGridItem(
        this.gridList[this.gridList.length - 1]
      )
      this.onSave()
    }, 100)
  }

  private updateActiveGridItem() {
    setTimeout(() => {
      VisualizationStore.setActiveSettingItem('chart')

      VisualizationStore.setCurrentGridItem(
        this.gridList[this.gridList.length - 1]
      )
    }, 100)
  }

  private async onDropTable(event: any, chartInfo?: string) {
    // chartInfo: widgetId & dataId $ dataType $chartType
    // chartInfo: dataId$dataType$chartType
    const compGridPos = this.calcXY({
      offsetLeft: event.clientX,
      offsetTop: event.clientY,
      dragOffset: this.dataStore.draggingOffset,
    })
    const compSize = this.calcWH(498, 300)
    const compMinSize = this.calcWH(400, 300)

    const title = this.getDefaultName('table')
    const chartInfoArray = (chartInfo || '').split('$')
    let find: any = null
    if (chartInfo) {
      find = VisualizationStore.pipelineWidgetList.find(
        (pipelineListItem: any) => {
          return (
            !pipelineListItem.id &&
            pipelineListItem.tid === Number(chartInfoArray[1]) &&
            pipelineListItem.type === 'task'
          )
        }
      )
    }
    const tableNew: GridItemDataInterface = {
      chartOptions: {
        parentId: this.dropTargetId,
        title,
        value: [],
        dataId: chartInfo ? chartInfoArray[1] : '',
        dataType: chartInfo ? chartInfoArray[2] : 'dataset',
        // column: [],
        curPage: 1,
        total: 0,
        name: '_record_id_',
        pageSize: 50,
        filters: [],
        sortCol: [],
        table: '',
      },
      chartType: 'table',
      x: compGridPos.x,
      y: compGridPos.y,
      w: compSize.w,
      h: compSize.h,
      minW: compMinSize.w,
      minH: compMinSize.h,
      i: `table${Math.random().toString(36).slice(-8)}`,
      resizable: true,
      moved: true,
      widgetType: chartInfo ? 'pipeline' : 'system',
      numberFormat: find?.numberFormat,
    }
    //  for save widget get widgetId
    const parameters = buildSaveParameters(tableNew)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }

    const response: any = await saveWidget(parameters) // 为table 创造 widgetId
    tableNew.widgetId = response.data.result

    if (find) {
      tableNew.chartOptions.pipelineId = find.pipelineId
      // eslint-disable-next-line prefer-destructuring
      tableNew.uniqueIDFromMyComponent = chartInfoArray[1] // 我的组件中的table无widgetId，用taskId来区分
    }

    VisualizationStore.addGridItem(tableNew)

    this.updateActiveGridItem()
    if (chartInfo) {
      this.getTableData(tableNew, undefined, false)
    }
    // table组件需要获取数据后再保存表格列等信息，延时控制画布保存
    setTimeout(() => {
      this.onSave()
    }, 300)
  }

  /**
   * 拖动放置算子控件组件
   */
  async onDropConfiguration(event: any, chartInfo?: string) {
    const compGridPos = this.calcXY({
      offsetLeft: event.clientX,
      offsetTop: event.clientY,
      dragOffset: this.dataStore.draggingOffset,
    })
    const compSize = this.calcWH(498, 300)
    const compMinSize = this.calcWH(200, 50)

    const chartInfoArray = (chartInfo || '').split('$')
    const find = VisualizationStore.pipelineWidgetList.find(
      (pipelineListItem: any) => {
        return (
          pipelineListItem.tid === Number(chartInfoArray[1]) &&
          pipelineListItem.type === 'configuration'
        )
      }
    )
    if (find) {
      const configuration: GridItemDataInterface = {
        chartOptions: {
          parentId: this.dropTargetId,
          chartType: 'configuration',
          params: find.data.params[0]?.formItem?.map((p: any) => ({
            ...p,
            default: find.data.params[0]?.formData[p.name],
            status: 'normal',
          })),
          info: find.data.info,
          pipelineId: find.pipelineId,
          taskId: find.tid,
          dataId: find.tid,
          title: find.name,
          titleIsShow: true,
          condition: find.data.params[0]?.condition,
          value: cloneDeep(find.data.params[0]?.formData),
        },
        chartType: 'configuration',
        x: compGridPos.x,
        y: compGridPos.y,
        w: compSize.w,
        h: compSize.h,
        i: `configuration${Math.random().toString(36).slice(-8)}`,
        minW: compMinSize.w,
        minH: compMinSize.h,
        resizable: true,
        moved: true,
        widgetType: 'pipeline',
      }
      const parameters = buildSaveParameters(configuration)
      if (this.dashBoardId) {
        parameters.data.dashboardId = this.dashBoardId
      }
      const response: any = await saveWidget(parameters) // 保存configuration widget
      configuration.widgetId = response.data.result

      VisualizationStore.addGridItem(configuration)

      this.updateActiveGridItem()
    }
  }

  /** 拖入geomap组件 */
  onDropGeoMap(event: any, chartInfo: string) {
    let myComp: KeyValueStore | null = null
    const dataId = chartInfo.split('$')[0] as string // 其实是taskId
    const find = VisualizationStore.pipelineWidgetList.find(
      (pipelineListItem: any) => {
        return pipelineListItem.id === Number(dataId)
      }
    )
    if (find) {
      myComp = cloneDeep(find.data)
      if (myComp) {
        const compGridPos = this.calcXY({
          offsetLeft: event.clientX,
          offsetTop: event.clientY,
          dragOffset: this.dataStore.draggingOffset,
        })
        const compSize = this.calcWH(400, 300)

        myComp.chartOptions.size = [400, 300]
        const valueKey = myComp.chartOptions.valueKey as any
        if (valueKey === '' || (isArray(valueKey) && valueKey.length === 0)) {
          myComp.chartOptions.valueKey = [
            { value: '', sort: 'asc', func: 'sum' },
          ]
        }
        // myComp.chartOptions.isVisualization = true
        myComp.x = compGridPos.x
        myComp.y = compGridPos.y
        myComp.w = compSize.w
        myComp.h = compSize.h
        myComp.chartOptions = {
          ...GeoMapDefaultConfig,
          ...myComp.chartOptions,
          parentId: this.dropTargetId,
        }
        myComp.chartOptions.pipelineName = find.pipelineName
        myComp.chartOptions.taskName = find.taskName
        myComp.chartOptions.tableNameReal = find.data.chartOptions.tableName
        myComp.i = `${
          chartInfo.split('|')[1] ? chartInfo.split('|')[1] : 'id'
        }${Math.random().toString(36).slice(-8)}`

        if (!myComp.chartOptions.title) {
          myComp.chartOptions.title = this.getDefaultName(GEOMAP_NAME)
        }
        myComp.dataQuery = {
          data: {
            id: myComp.chartOptions.dataId,
            type: myComp.chartOptions.dataType,
            widgetJson: {
              ...myComp.widgetJson,
            },
          },
        }

        VisualizationStore.addGridItem(myComp as gridItemFilterInterface)

        this.updateActiveGridItem()

        this.getWidgetData(
          myComp.chartOptions,
          myComp as GridItemDataInterface,
          this.gridList.length - 1,
          true
        )
      }
    }
  }

  /**
   * gis 视图
   */
  async onDropGisView(event: any, chartInfo: string) {
    let myComp: any = null
    const compSize = this.calcWH(400, 300)
    const compMinSize = this.calcWH(400, 300)
    const chartInfoArray = (chartInfo || '').split('$')
    const compGridPos = this.calcXY({
      offsetLeft: event.clientX,
      offsetTop: event.clientY,
      dragOffset: this.dataStore.draggingOffset,
    })
    myComp = {
      chartOptions: {
        parentId: this.dropTargetId,
        value: [],
        dataId: chartInfo ? chartInfoArray[1] : '',
        tid: chartInfo ? chartInfoArray[1] : '',
        projectId: this.projectId,
        dataType: chartInfo ? chartInfoArray[2] : 'dataset',
        // column: [],
      },
      chartType: ['gis', GIS_VIEW],
      x: compGridPos.x,
      y: compGridPos.y,
      w: compSize.w,
      h: compSize.h,
      minW: compMinSize.w,
      minH: compMinSize.h,
      i: `gis${Math.random().toString(36).slice(-8)}`,
      resizable: true,
      moved: true,
      widgetType: chartInfo ? 'pipeline' : 'system',
    }
    //  for save widget get widgetId
    const parameters = buildSaveParameters(myComp)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }

    const response: any = await saveWidget(parameters) //  创造 widgetId
    myComp.widgetId = response.data.result

    VisualizationStore.addGridItem(myComp as gridItemFilterInterface)

    this.updateActiveGridItem()
  }

  onDropMycomponent(event: any, chartInfo: string) {
    console.log(chartInfo)
    /* eslint-disable */
    // 接入geoMap
    if (chartInfo.indexOf(GEOMAP_NAME) > -1) {
      this.onDropGeoMap(event, chartInfo)
      return
    }
    if (chartInfo.indexOf('table') > -1) {
      this.onDropTable(event, chartInfo)
      return
    }
    if (chartInfo.indexOf('configuration') > -1) {
      this.onDropConfiguration(event, chartInfo)
      return
    }
    if (chartInfo.indexOf(GIS_VIEW) > -1) {
      this.onDropGisView(event, chartInfo)
      return
    }
    let myComp: KeyValueStore | null = null
    const chartInfoArr = chartInfo.split('$') // widgetId&dataId$chartType
    const widgetId = chartInfoArr[0] as string // id 可能为null
    const dataId = chartInfoArr[1] as string // tid
    const widgetType = chartInfoArr[3] as string // widgetType
    const find =
      widgetId === 'null'
        ? VisualizationStore.pipelineWidgetList.find(
            (pipelineListItem: any) => {
              return (
                pipelineListItem.id === null &&
                pipelineListItem.tid === Number(dataId)
              )
            }
          )
        : VisualizationStore.pipelineWidgetList.find(
            (pipelineListItem: any) => {
              return pipelineListItem.id === Number(widgetId)
            }
          )
    if (find) {
      // find.data.formData = find.data.chartOptions
      myComp = cloneDeep(find.data)
      if (myComp) {
        const compGridPos = this.calcXY({
          offsetLeft: event.clientX,
          offsetTop: event.clientY,
          dragOffset: this.dataStore.draggingOffset,
        })
        const compSize = this.calcWH(400, 300)

        myComp.dataId = find.tid
        myComp.dataType = find.type
        myComp.widgetType = widgetType
        // 从我的组件里拖动到画布的源头图表id记录到gridItem，支持我的组件到画布图表的关联
        myComp.uniqueIDFromMyComponent = widgetId
        // myComp.formData.dataId = find.tid
        myComp.chartOptions.pipelineId = find.pipelineId
        myComp.chartOptions.size = [400, 300]

        const detailDefaultOptions = getDetailDefaultOptionsByChartType(
          myComp.chartType
        )

        myComp.chartOptions = {
          ...detailDefaultOptions,
          ...defaultChartoptions,
          ...myComp.chartOptions,
          padding: [10, 20, 20, 10],
          xAxisIsShow: true, // 强制显示
          yAxisIsShow: true,
          themeSetting: this.themeSetting,
          parentId: this.dropTargetId,
        }

        myComp.x = compGridPos.x
        myComp.y = compGridPos.y
        myComp.w = compSize.w
        myComp.h = compSize.h

        delete myComp.chartOptions.xAxisIsShow
        delete myComp.chartOptions.axisGridIsShow
        myComp.i = `${
          chartInfo.split('|')[1] ? chartInfo.split('|')[1] : 'id'
        }${Math.random().toString(36).slice(-8)}`

        myComp.dataQuery = {
          data: {
            id: myComp.chartOptions.dataId,
            type: myComp.chartOptions.dataType,
            widgetJson: {
              ...myComp.widgetJson,
            },
          },
        }

        //  fix pieChart / donutChart padding issue
        if (myComp.chartType.includes('pieChart')) {
          myComp.chartOptions.padding = [10, 20, 30, 10]
        }

        VisualizationStore.addGridItem(myComp as GridItemDataInterface)

        parseChartOptionToFunction(myComp.chartOptions, myComp.chartType)
        this.updateActiveGridItem()
        this.getWidgetData(
          myComp.chartOptions,
          myComp as GridItemDataInterface,
          this.gridList.length - 1,
          true
        )
      }
    } else {
      this.$message.error('该组件不存在或已被删除')
    }
  }

  /** 筛选器组件 */
  private async onDropFilterForm(event: any) {
    const chartType = filterFormTypeName
    const compGridPos = this.calcXY({
      offsetLeft: event.clientX,
      offsetTop: event.clientY,
      dragOffset: this.dataStore.draggingOffset,
    })
    const compSize = this.calcWH(500, 300)
    const compMinSize = this.calcWH(288, 160)

    const title = this.getDefaultName(chartType)
    const configuration: GridItemDataInterface = {
      chartOptions: {
        parentId: this.dropTargetId,
        chartType: [chartType, chartType],
        title,
        startNode: '',
        filterColumns: [{ value: '', type: 'string' }],
        formData: {},
      },
      chartType: chartType,
      x: compGridPos.x,
      y: compGridPos.y,
      w: compSize.w,
      h: compSize.h,
      i: `filterForm${Math.random().toString(36).slice(-8)}`,
      minW: compMinSize.w,
      minH: compMinSize.h,
      resizable: true,
      moved: true,
      widgetType: 'system',
    }
    const parameters = buildSaveParameters(configuration)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }
    const response: any = await saveWidget(parameters) // 保存configuration widget
    configuration.widgetId = response.data.result

    VisualizationStore.addGridItem(configuration)

    setTimeout(() => {
      VisualizationStore.setCurrentGridItem(
        this.gridList[this.gridList.length - 1]
      )
      this.onSave()
    }, 100)
  }

  async onDropOtherSystemComponent(event: any, chartInfo: string) {
    const compGridPos = this.calcXY({
      offsetLeft: event.clientX,
      offsetTop: event.clientY,
      dragOffset: this.dataStore.draggingOffset,
    })
    const compSize = this.calcWH(400, 300)
    const compMinSize = this.calcWH(400, 300)

    // 默认尺寸
    const size = [400, 300]
    const chartType = chartInfo.split('|')
    const detailDefaultOptions = getDetailDefaultOptionsByChartType(chartType)
    const dataItem: GridItemDataInterface = {
      chartType,
      widgetType: 'system',
      // formData: formDataDefault,
      size,
      chartOptions: {
        ...detailDefaultOptions, // !!!!!!!! 默认设置，可能需要根据chart类型来区分，地图、graph等 !!!!!!!!!!!
        ...defaultChartoptions,
        // visibleItemsNumber: 10000000,
        // labelTextMaxlength: 10,
        title: this.getDefaultName(chartInfo),
        showChart: false,
        padding: [10, 20, 20, 10],
        value: [],
        size,
        dataType: 'dataset',
        themeSetting: this.themeSetting,
        parentId: this.dropTargetId,
      },
      x: compGridPos.x,
      y: compGridPos.y,
      w: compSize.w,
      h: compSize.h,
      minW: compMinSize.w,
      minH: compMinSize.h,
      i: `${
        chartInfo.split('|')[1] ? chartInfo.split('|')[1] : 'id'
      }${Math.random().toString(36).slice(-8)}`,
      resizable: true,
      moved: true,
    }
    //  prepare for interact feat in preview mode
    dataItem.dataQuery = {
      data: {
        id: dataItem.chartOptions.dataId,
        type: dataItem.chartOptions.dataType,
        widgetJson: {
          isAggr: isAggrStatus.DEFAULT,
          config: {
            topN: chartType.includes('pieChart') ? 10000 : 1000,
            filters: [],
            keys: [],
            values: [],
          },
        },
      },
    }

    // dataItem.formData.dataType = ChartDataTypeEnum.system
    if (chartInfo === 'barChart|stackBarChart') {
      dataItem.chartOptions.barIsStack = true
    }
    //  fix pieChart / donutChart padding issue
    if (chartInfo.includes('pieChart')) {
      dataItem.chartOptions.padding = [10, 20, 30, 10]
    }
    if (GRAPH_ARRAY.includes(chartType[1])) {
      dataItem.chartOptions.padding = [10, 10, 0, 0]
    }

    VisualizationStore.addGridItem(dataItem)
    this.updateActiveGridItem()
  }

  async onDropVistabs(event: any) {
    if (
      this.targetItem &&
      this.targetItem.chartOptions.chartType === vistabsTypeName
    ) {
      this.$message.info('Tabs控件目前不支持多级嵌套！')
      return
    }

    const chartType = vistabsTypeName
    const compGridPos = this.calcXY({
      offsetLeft: event.clientX,
      offsetTop: event.clientY,
      dragOffset: this.dataStore.draggingOffset,
    })
    const compSize = this.calcWH(800, 400)
    const compMinSize = this.calcWH(288, 160)

    const tabIds = [
      `vistabs-tab-${Math.random().toString(36).slice(-8)}`,
      `vistabs-tab-${Math.random().toString(36).slice(-8)}`,
    ]
    const configuration: GridItemDataInterface = {
      chartOptions: {
        parentId: this.dropTargetId,
        chartType,
        mainTitle: '',
        activeTabId: '',
        tabs: [
          {
            id: tabIds[0],
            title: '页面1',
          },
          {
            id: tabIds[1],
            title: '页面2',
          },
        ],
      },
      chartType,
      x: compGridPos.x,
      y: compGridPos.y,
      w: compSize.w,
      h: compSize.h,
      i: `vistabs${Math.random().toString(36).slice(-8)}`,
      minW: compMinSize.w,
      minH: compMinSize.h,
      resizable: true,
      moved: true,
      widgetType: 'system',
    }
    const parameters = buildSaveParameters(configuration)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }
    const response: any = await saveWidget(parameters) // 保存configuration widget
    configuration.widgetId = response.data.result

    VisualizationStore.addGridItem(configuration)

    setTimeout(() => {
      VisualizationStore.setActiveSettingItem('chart')

      VisualizationStore.setCurrentGridItem(
        this.gridList[this.gridList.length - 1]
      )
      this.onSave()
    }, 100)
  }

  private movedItem = { i: '', x: -1, y: -1, scale: 1 }
  private mouseXY = { x: -1, y: -1 }
  clearMovedItem() {
    this.movedItem.i = ''
    this.movedItem.x = -1
    this.movedItem.y = -1
    this.movedItem.scale = 1
  }
  listenGridItemMove(e: any) {
    this.mouseXY.x = e.clientX
    this.mouseXY.y = e.clientY
  }
  itemMoving(gridItemId: string) {
    if (this.movedItem.x < 0) {
      document.addEventListener('mousemove', this.listenGridItemMove)
      this.movedItem.i = gridItemId
    }
  }

  getItemMoveTarget(
    targetCollisonLayouts: any,
    mousePos: { x: number; y: number }
  ) {
    // 碰撞坐标计算，获得冲突目标：基于第一层画布范围，将当前鼠标释放坐标位置转换为画布上的网格坐标
    const temp_pos = this.calcXY({
      offsetLeft: mousePos.x,
      offsetTop: mousePos.y,
      dragOffset: this.dataStore.draggingOffset,
    })

    // 初始化拖动元素的坐标范围
    const movedItemRange = {
      x: temp_pos.x,
      y: temp_pos.y,
      w: 1,
      h: 1,
    }
    return this.getFirstCollision(targetCollisonLayouts, movedItemRange)
  }

  calculateDragScale(item: GridItemDataInterface) {
    let sourceItem = undefined
    if (item.chartOptions.parentId !== 'root') {
      this.gridList.find((gridItem) => {
        if (gridItem.chartType === vistabsTypeName) {
          const tab = gridItem.chartOptions.tabs.find(
            (tab: any) => tab.id === item.chartOptions.parentId
          )
          if (tab) {
            sourceItem = gridItem
          }
        }
      })
    }
    const sourceContainer = this.getTargetContainerRect(sourceItem)
    const targetContainer = this.getTargetContainerRect(this.targetItem)

    return parseFloat(
      (targetContainer!.width / sourceContainer!.width).toFixed(1)
    )
  }

  itemMoveEnd(item: GridItemDataInterface) {
    this.targetItem = undefined

    // 获取冲突判断的元素范围：仅从第一层网格元素中过滤，刨除拖动元素本身
    const mousePos = {
      x: this.mouseXY.x,
      y: this.mouseXY.y,
    }

    // 计算鼠标移动时的偏移量
    const itemDom = this.getTargetContainerRect(item)
    this.dataStore.setDraggingOffset({
      x: mousePos.x - itemDom!.x,
      y: mousePos.y - itemDom!.y,
    })

    const collisionItems = this.rootLayout.filter(
      (gridItem) => gridItem.i !== item.i
    )
    this.targetItem = this.getItemMoveTarget(collisionItems, mousePos)

    // 释放的目标是组件容器元素
    if (this.targetItem) {
      /**
       * 1.检查目标是否为tabs容器：chartType=vistabs
       * 2.如果目标是自身则跳过；
       * 3.如果拖动对象和释放目标都是tabs，则跳过，暂不支持多层嵌套；
       * 4.如果当前tab页与拖动目标的父亲ID是一样，则跳过
       */
      // ；
      if (
        this.targetItem.chartOptions.chartType !== vistabsTypeName ||
        this.targetItem.i === item.i ||
        item.chartType === vistabsTypeName ||
        this.targetItem.chartOptions.activeTabId === item.chartOptions.parentId
      ) {
        //
        this.clearMovedItem()
        return
      }

      // 保存目标容器ID编号
      this.dropTargetId = this.targetItem.chartOptions.activeTabId
    } else {
      // 释放目标是第一层grid-layout
      // 释放之前就在第一层上则跳过
      if (item.chartOptions.parentId === 'root') {
        this.clearMovedItem()
        return
      }

      // 保存目标容器ID为第一层
      this.dropTargetId = 'root'
    }

    // 更改移动组件在子组件列表中的归属关系
    if (item) {
      // 第二次计算，获得释放后真正的网格坐标：基于释放目标的宽度范围，自动调整网格坐标。保存的坐标值在
      const newPos = this.calcXY({
        offsetLeft: this.mouseXY.x,
        offsetTop: this.mouseXY.y,
        dragOffset: this.dataStore.draggingOffset,
        innerW: item.w,
      })

      this.movedItem.i = item.i.toString()
      this.movedItem.x = newPos.x
      this.movedItem.y = newPos.y
      this.movedItem.scale = this.calculateDragScale(item)

      // 更改移动数据的parentId
      item.chartOptions.parentId = this.dropTargetId

      // 清除移动元素记录的状态
      document.removeEventListener('mousemove', this.listenGridItemMove)
    }
  }

  calcXY(params: any) {
    const { offsetLeft, offsetTop, dragOffset, innerW } = params
    const targetContainer = this.getTargetContainerRect(this.targetItem)
    const containerWidth = targetContainer!.width
    const margin = this.dataStore.girdMargin
    const cols = this.dataStore.gridColNum
    const rowHeight = this.dataStore.gridRowHeight
    const colWidth = (containerWidth - margin[0] * (cols + 1)) / cols
    let x = Math.round(
      (offsetLeft - targetContainer!.left - dragOffset.x - margin[0]) /
        (colWidth + margin[0])
    )
    let y = Math.round(
      (offsetTop - targetContainer!.top - dragOffset.y - margin[1]) /
        (rowHeight + margin[1])
    )

    if (innerW) {
      x = Math.max(Math.min(x, cols - innerW), 0)
    } else {
      x = Math.max(x, 0)
      y = Math.max(y, 0)
    }

    return { x, y }
  }
  calcWH(width: number, height: number) {
    const margin = this.dataStore.girdMargin

    let w = Math.round((width + margin[0]) / (this.unitX + margin[0]))
    let h = Math.round((height + margin[1]) / (this.unitY + margin[1]))

    return { w, h }
  }

  collides(l1: any, l2: any) {
    if (l1 === l2) return false // same element
    if (l1.x + l1.w <= l2.x) return false // l1 is left of l2
    if (l1.x >= l2.x + l2.w) return false // l1 is right of l2
    if (l1.y + l1.h <= l2.y) return false // l1 is above l2
    if (l1.y >= l2.y + l2.h) return false // l1 is below l2
    return true // boxes overlap
  }

  private targetItem: any = undefined
  getFirstCollision(layout: any, layoutItem: any) {
    for (let i = 0, len = layout.length; i < len; i++) {
      if (this.collides(layout[i], layoutItem)) {
        return layout[i]
      }
    }
  }

  getTargetContainerRect(targetItem?: any) {
    if (targetItem) {
      const childList = document.getElementsByClassName('grid-item')
      for (let i = 0; i < childList.length; i++) {
        const domId = 'grid-item-' + targetItem.i
        if (childList[i].id === domId) {
          return childList[i].getBoundingClientRect()
        }
      }
    } else {
      return document
        .getElementsByClassName('grid-layout-design')[0]
        .getBoundingClientRect()
    }
  }

  checkDragDropTarget(event: any) {
    this.dropTargetId = 'root'
    this.targetItem = undefined
    // 重新计算当前的容器是否有变化
    const mousePos = {
      x: event.clientX,
      y: event.clientY,
    }

    this.targetItem = this.getItemMoveTarget(this.rootLayout, mousePos)

    if (this.targetItem) {
      // 检查目标是否为容器：chartType=vistabs
      if (this.targetItem.chartOptions.chartType !== vistabsTypeName) {
        return
      }

      // 保存目标容器的ID编号
      this.dropTargetId = this.targetItem.chartOptions.activeTabId
    }
  }

  onDrop(event: any) {
    if (this.isVisitor) {
      return
    }
    //检测拖放组件释放时所在目标，如果时容器目标则计算在该容器中对应的栅格坐标
    this.checkDragDropTarget(event)

    const chartMapKeys = Object.keys(chartDetailToVisClassMaps)
    if (event) {
      const chartInfo = event.dataTransfer.getData('text')
      if (
        (chartInfo.includes('dagre') || chartInfo.includes('graphLineChart')) &&
        !UserStore.isProjectOwner
      ) {
        this.$message.info(
          '因图分析及时空分析等模块未开放协作，暂不支持非项目创建者绘制此类图表'
        )
        return
      }

      if (chartInfo) {
        if (/\$/.test(chartInfo)) {
          this.onDropMycomponent(event, chartInfo)
        } else if (chartInfo === 'table|table') {
          this.onDropTable(event)
        } else if (chartInfo === 'text|text') {
          this.onDropText(event)
        } else if (chartInfo === 'filterForm|filterForm') {
          this.onDropFilterForm(event)
        } else if (chartInfo === 'vistabs|vistabs') {
          this.onDropVistabs(event)
        } else if (
          chartMapKeys.some((key) =>
            chartInfo.toLocaleLowerCase().includes(key.toLocaleLowerCase())
          )
        ) {
          this.onDropOtherSystemComponent(event, chartInfo)
        }
      }
    }
  }

  /**
   * layout 组件更新完成回调
   */
  layoutUpdatedEvent(/* newLayout: IGridItemType[] */) {
    if (this.movedItem.x >= 0) {
      let gridItem: any = this.dataStore.gridList.find(
        (item) => item.i === this.movedItem.i
      )
      gridItem!.x = this.movedItem.x
      gridItem!.y = this.movedItem.y
      const itemSize = gridItem.chartOptions.size
      itemSize[0] = itemSize[0] * this.movedItem.scale

      this.clearMovedItem()

      const parameters = buildSaveParameters(gridItem)
      if (this.dashBoardId) {
        parameters.data.dashboardId = this.dashBoardId
      }
      if (gridItem.widgetId) {
        updateWidget(parameters) // 保存有嵌套层级关系的widget
      }
    }
    this.onSave()
  }

  public dashBoardId: string | number | undefined = undefined

  // 若value 中存在公式，则替换为公式的值
  public transferTextValue(str: string) {
    const htmlTemplate = str

    const domparser = new DOMParser()
    const doc = domparser.parseFromString(htmlTemplate, 'text/html') // [object HTMLDocument]

    const elements = doc.querySelectorAll('.formula-style')
    if (elements.length > 0) {
      //  replace text with data-result value with innerText
      //  remove .formula-style, add result-style
      elements.forEach((item: any) => {
        item.textContent = item.getAttribute('data-result')
        item.classList.remove('formula-style')
        item.classList.add('result-style')
      })
    }

    const html = doc.documentElement.outerHTML
    const pre = html.indexOf('<body>') + '<body>'.length
    const after = html.indexOf('</body>')

    return html.slice(pre, after)
  }

  /**
   * 保存数据
   *  @param 保存成功后消息提示 - 默认不提示消息
   *  @param 保存操作后的回调函数
   *  @param 更新tab name 所需参数
   * */
  onSave(noMessage: boolean = true, callback?: Function, name?: string) {
    const layout: Array<any> = []
    const filters: Array<any> = []
    const tables: Array<any> = []
    const text: Array<any> = []
    this.gridList.forEach((gridItem: GridItemDataInterface) => {
      if (gridItem.chartType === 'filter') {
        const gridFilter = cloneDeep(gridItem) as gridItemFilterInterface
        const sendItem: KeyValueStore = {
          chartType: gridFilter.chartType,
          dataId: gridFilter.dataId,
          tableName: gridFilter.tableName,
          layout: gridFilter.layout,
          i: gridFilter.i,
          x: gridFilter.x,
          y: gridFilter.y,
          w: gridFilter.w,
          h: gridFilter.h,
          minW: gridFilter.minW,
          minH: gridFilter.minH,
          fieldConfig: gridFilter.fieldConfig.reduce(
            (rec: Array<any>, fieldInfo: KeyValueStore) => {
              const item = fieldInfo
              delete item.componentId
              delete item.undefined
              item.column = fieldInfo.column.value
              // item.columnType = fieldInfo.column.type
              delete item.undefined
              delete item.value
              rec.push(item)
              return rec
            },
            []
          ),
        }
        filters.push(sendItem)
      } else if (gridItem.chartType === 'table') {
        tables.push({
          ...gridItem,
          chartOptions: {
            ...(gridItem.chartOptions || {}),
            value: [],
          },
        })
      } else if (gridItem.chartType === 'text') {
        gridItem.config = []
        layout.push({
          ...gridItem,
          chartOptions: {
            ...(gridItem.chartOptions || {}),
            value: '',
          },
        })
      } else if (gridItem.chartType === vistabsTypeName) {
        layout.push({
          ...gridItem,
        })
      } else if (
        gridItem.chartType === 'filterForm' ||
        (gridItem.chartType as string[]).includes('filterForm')
      ) {
        const filterFormItem = {
          chartType: Array.isArray(gridItem.chartType)
            ? gridItem.chartType.join('|')
            : gridItem.chartType,
          // chartOptions: cloneDeep(gridItem.chartOptions),
          x: gridItem.x,
          y: gridItem.y,
          w: gridItem.w,
          h: gridItem.h,
          widgetType: gridItem.widgetType,
          minW: gridItem.minW,
          minH: gridItem.minH,
          i: gridItem.i,
          widgetId: gridItem.widgetId,
        }
        layout.push(filterFormItem)
      } else {
        //  拖入的系统图表组件未配置的话，不会进入保存layout的payload
        if (gridItem.widgetId) {
          layout.push({
            chartType: Array.isArray(gridItem.chartType)
              ? gridItem.chartType.join('|')
              : gridItem.chartType,
            x: gridItem.x,
            y: gridItem.y,
            w: gridItem.w,
            h: gridItem.h,
            widgetType: gridItem.widgetType,
            minW: gridItem.minW,
            minH: gridItem.minH,
            i: gridItem.i,
            widgetId: gridItem.widgetId,
            uniqueIDFromMyComponent: gridItem.uniqueIDFromMyComponent,
          })
        }
      }
    })
    let apiQuery = saveDashboard
    const parameters: KeyValueStore = {
      layout: JSON.stringify({
        w: VisualizationStore.layoutData.width,
        h: VisualizationStore.layoutData.height,
        background: VisualizationStore.layoutData.background,
        themeSetting: VisualizationStore.layoutData.themeSetting,
        scale: VisualizationStore.scaleValue,
        filters,
        tables,
        text,
        gridItems: layout,
        coordinations: VisualizationStore.coordinations,
      }),
      projectId: this.projectId,
    }
    if (this.dashBoardId) {
      parameters.id = this.dashBoardId
      apiQuery = updateDashboard
      if (name) {
        parameters.name = name
      }
    }
    /**
     * 同步widget信息，保证每次dashboard与widget内容同步
     */
    // VisualizationStore.gridList.forEach((gridItem,index) => {
    //   VisualizationStore.updateWidget({ gridItem, index })
    // })
    apiQuery({ data: { ...parameters } }).then((response) => {
      if (!noMessage) {
        this.$message.success('保存成功')
      }
      //  更新dashboard
      if (response.data.code === 100 && this.dashBoardId) {
        //  更新tabs 中相应tab layout
        const currentTab = this.sysTabs.find(
          (item: any) => item.id === this.dashBoardId
        )
        this.$set(currentTab, 'layout', response.data.result.layout)
      }

      //  新增dashboard
      if (response.data.code === 100 && !this.dashBoardId) {
        this.dashBoardId = response.data.result.id
        VisualizationStore.setSysTabs([response.data.result])
        VisualizationStore.setCurrentDashboardData(response.data.result)
      }
      if (callback) {
        callback()
      }
    })
  }

  public get sysTabs() {
    return VisualizationStore.sysTabs
  }

  flexLayout() {
    const widgetLayout = this.$refs.widgetLayout as any
    if (widgetLayout) {
      // 先调整整体的布局
      widgetLayout.flexLayout()

      // 布局调整结束后再调整所有图表自身的大小
      setTimeout(() => {
        widgetLayout.resizedAllItems()
        // 调整结束后整体保存dashboard信息
        this.onSave(true)
      }, 100)
    }
  }

  /**
   * 初始化监听控件的运行结果
   */
  private initConfigurationWS() {
    this.$socket.on('queryStatus', this.parseConfigurationWS)
  }

  /**
   * 解析控件执行结果
   */
  private parseConfigurationWS(data: any) {
    if (!this.configurationExecuteId) {
      return
    }
    const pipelineId = this.gridList.filter(
      (list) => list.widgetId === this.configurationExecuteId
    )[0].chartOptions.pipelineId
    if (data.status === 'SUCCESS') {
      this.$message.info('控件执行成功，正在更新相关图表数据', 4)
      if (pipelineId) {
        //  update for formulaList result
        VisualizationStore.fetchFormulaList({
          projectId: Number(this.$route.params.id),
        })

        this.gridList.forEach((gridItem, index) => {
          if (
            gridItem.widgetId !== this.configurationExecuteId &&
            gridItem.chartOptions.pipelineId === pipelineId && // pipeline 下的其他chart获取数据
            gridItem.chartType !== 'configuration'
          ) {
            this.getWidgetData(
              gridItem.chartOptions as KeyValueStore,
              gridItem,
              index
            )
          }
        })
        setTimeout(() => {
          this.saveAllTextValue()
        }, 500)
      }
    } else {
      this.$message.error('控件执行失败，请检查配置稍候再试', 4)
    }
    this.dataStore.setConfigurationExecuteId('')
  }

  beforeDestroy() {
    this.dataStore.setActiveSettingItem('background')
    // 退出之前销毁缓存，防止节点数据改变后查询数据不变
    visGraphService.clearCache()
    this.dataStore.setPipelineId('')
    this.dataStore.setDataset([])
    this.dataStore.setSysTabs([])
    this.destroyEventBus()
    this.defaultThemeConfig()
    this.$socket.off('queryStatus', this.parseConfigurationWS)
  }

  private async queryNewPipelineWidgetList(newPipelineId: string) {
    // 查询复制pipeline下的widget信息，主要获取Configuration的新配置
    const response: any = await widgetQueryByProject({
      data: {
        projectId: this.projectId,
        pipelineId: newPipelineId,
      },
    })
    if (response.data.code === 100) {
      return response.data.result[1].taskWidgets || []
    }
    return []
  }

  /** 移动过程中检测 */
  public onGridItemResizeMove = (grid: any) => {
    this.checkDragingLine(grid, true)
  }

  /** 根据GridItem实力 */
  private getDomRect(gridCompInstance: any): DomReact {
    const [x = 0, y = 0] = (
      gridCompInstance.style.transform
        .replace('translate3d(', '')
        .split(',') as any[]
    )
      .filter((_, index) => {
        return index < 2
      })
      .map((item) => {
        return parseInt(item)
      })
    return {
      x,
      y,
      width: parseInt(gridCompInstance.style.width),
      height: parseInt(gridCompInstance.style.height),
    }
  }

  /** 参考线检测 */
  private checkDragingLine(grid: any, checkFlag?: boolean) {
    const gridItems = document.getElementsByClassName('grid-item')
    // 先判断，可以在只剩下一个组件的情况下提升性能
    if (gridItems.length < 2) {
      return
    }
    this.dataStore.setDraggingItem(grid)
    const [x = 0, y = 0] = (
      grid.style.transform.replace('translate3d(', '').split(',') as any[]
    )
      .filter((_, index) => {
        return index < 2
      })
      .map((item) => {
        return parseInt(item)
      })

    this.draggingDomRect = {
      x,
      y,
      width: parseInt(grid.style.width),
      height: parseInt(grid.style.height),
    }

    this.checkAlignMent(checkFlag)
  }

  /** 组件拖拽过程检测 */
  public onGridItemDragMove(grid: any) {
    this.checkDragingLine(grid)
  }

  /** 碰撞检测 */
  private findTag(
    ohterReactItems: any[],
    attrOhterSort: string,
    attrOffset: string,
    maxOffset: number = 5
  ) {
    return ohterReactItems
      .sort((a, b) => {
        return a[attrOhterSort] - b[attrOhterSort]
      })
      .find((item) => {
        return item[attrOffset] < maxOffset
      })
  }

  /** 获取横向参考线样式 */
  private getHlineStyle(
    findData: DomReact,
    draggingData: DomReact,
    ohterGridDomReact: DomReact[],
    tagAttr: string
  ) {
    const matchItems = ohterGridDomReact.filter(
      (item) => item[tagAttr] == findData[tagAttr]
    )
    matchItems.push(draggingData)
    const minX = matchItems.sort((a, b) => {
      return a.x - b.x
    })[0].x
    const maxXItem = matchItems.sort((a, b) => {
      return b.x - a.x
    })[0]
    const maxX = maxXItem.x + maxXItem.width
    return {
      width: `${maxX - minX}px`,
      transform: `translateX(${minX}px)`,
    }
  }

  /** 获取纵向参考线样式 */
  private getVlineStyle(
    findData: DomReact,
    draggingData: DomReact,
    ohterGridDomReact: DomReact[],
    tagAttr: string
  ) {
    const matchItems = ohterGridDomReact.filter(
      (item) => item[tagAttr] == findData[tagAttr]
    )
    matchItems.push(draggingData)
    const minY = matchItems.sort((a, b) => {
      return a.y - b.y
    })[0].y
    const maxYItem = matchItems.sort((a, b) => {
      return b.y - a.y
    })[0]
    const maxY = maxYItem.y + maxYItem.height
    return {
      height: `${maxY - minY}px`,
      transform: `translateY(${minY}px)`,
    }
  }

  private checkAlignMent(noCheckFlag?: boolean) {
    const gridItems = document.getElementsByClassName('grid-item')
    const compItems = [].slice.call(gridItems)
    const { draggingItem, draggingDomRect } = this
    const flag = noCheckFlag ? true : this.draggingFlag

    if (flag && draggingItem && draggingDomRect && compItems?.length > 1) {
      const draggingItem = this.draggingItem
      const otherGridItems = compItems.filter(
        (item: any) => item.id !== this.draggingItem?.$attrs.id
      )
      /** 计算出拖拽grid与其他grid的offset数据以及预设定位数据 */
      const ohterGridDomReact: DomReact[] = otherGridItems.map((item) => {
        const itemRect = this.getDomRect(item)
        return {
          ...itemRect,
          ht: itemRect.y,
          hc: itemRect.y + itemRect.height / 2,
          hb: itemRect.y + itemRect.height,
          vl: itemRect.x,
          vc: itemRect.x + itemRect.width / 2,
          vr: itemRect.x + itemRect.width,
          offsetHt: Math.abs(itemRect.y - draggingDomRect.y),
          offsetHc: Math.abs(
            itemRect.y +
              itemRect.height / 2 -
              (draggingDomRect.y + draggingDomRect.height / 2)
          ),
          offsetHb: Math.abs(
            itemRect.y +
              itemRect.height -
              (draggingDomRect.y + draggingDomRect.height)
          ),
          offsetVl: Math.abs(itemRect.x - draggingDomRect.x),
          offsetVc: Math.abs(
            itemRect.x +
              itemRect.width / 2 -
              (draggingDomRect.x + draggingDomRect.width / 2)
          ),
          offsetVr: Math.abs(
            itemRect.x +
              itemRect.width -
              (draggingDomRect.x + draggingDomRect.width)
          ),
        }
      })

      /** 检测水平头部是否达到吸附条件 */
      const indexFindHt = this.findTag(
        ohterGridDomReact,
        'offsetHt',
        'offsetHt',
        4
      )

      if (indexFindHt) {
        this.htStyle = this.getHlineStyle(
          indexFindHt,
          draggingDomRect,
          ohterGridDomReact,
          'offsetHt'
        )
        this.ht = indexFindHt.ht
        this.alignmentFlagHT = true
      } else {
        this.alignmentFlagHT = false
      }

      /** 检测水平中部是否达到吸附条件 */
      const indexFindHc = this.findTag(
        ohterGridDomReact,
        'offsetHc',
        'offsetHc',
        4
      )
      if (indexFindHc) {
        this.hcStyle = this.getHlineStyle(
          indexFindHc,
          draggingDomRect,
          ohterGridDomReact,
          'offsetHc'
        )
        this.hc = indexFindHc.hc
        this.alignmentFlagHC = true
      } else {
        this.alignmentFlagHC = false
      }

      /** 检测水平底部是否达到吸附条件 */
      const indexFindHb = this.findTag(
        ohterGridDomReact,
        'offsetHb',
        'offsetHb',
        4
      )
      if (indexFindHb) {
        this.hbStyle = this.getHlineStyle(
          indexFindHb,
          draggingDomRect,
          ohterGridDomReact,
          'offsetHb'
        )
        this.hb = indexFindHb.hb
        this.alignmentFlagHB = true
      } else {
        this.alignmentFlagHB = false
      }

      /** 检测垂直左侧是否达到吸附条件 */
      const indexFindVl = this.findTag(
        ohterGridDomReact,
        'offsetVl',
        'offsetVl',
        4
      )
      if (indexFindVl) {
        this.vlStyle = this.getVlineStyle(
          indexFindVl,
          draggingDomRect,
          ohterGridDomReact,
          'offsetVl'
        )
        this.vl = indexFindVl.vl
        this.alignmentFlagVL = true
      } else {
        this.alignmentFlagVL = false
      }
      /** 检测垂直中部是否达到吸附条件 */
      const indexFindVc = this.findTag(
        ohterGridDomReact,
        'offsetVc',
        'offsetVc',
        4
      )
      if (indexFindVc) {
        this.vcStyle = this.getVlineStyle(
          indexFindVc,
          draggingDomRect,
          ohterGridDomReact,
          'offsetVc'
        )
        this.vc = indexFindVc.vc
        this.alignmentFlagVC = true
      } else {
        this.alignmentFlagVC = false
      }

      /** 检测垂直中部是否达到吸附条件 */
      const indexFindVr = this.findTag(
        ohterGridDomReact,
        'offsetVr',
        'offsetVr',
        4
      )
      if (indexFindVr) {
        this.vrStyle = this.getVlineStyle(
          indexFindVr,
          draggingDomRect,
          ohterGridDomReact,
          'offsetVr'
        )
        this.vr = indexFindVr.vr
        this.alignmentFlagVR = true
      } else {
        this.alignmentFlagVR = false
      }
    }
  }

  /** 检测是否是筛选组件 */
  checkIsFilterForm(gridItem: GridItemDataInterface) {
    return checkIsFilterForm(gridItem)
  }

  /** 获取gridIndex */
  getGridIndex(attrCompare: string | number, attr: string = 'i') {
    return this.gridList.findIndex((item: any) => item[attr] === attrCompare)
  }

  get pipelineId() {
    return this.dataStore.pipelineId
  }

  get dataset(): any[] {
    return this.dataStore.dataset
  }

  private async initAllWidgetList() {
    if (VisualizationStore.pipelineId) {
      const widgetList = await this.queryNewPipelineWidgetList(
        VisualizationStore.pipelineId as string
      )
      this.dataStore.setAllWidgetList(widgetList)
      return widgetList
    }
    return []
  }
  /**
   * 组件复制具体调用逻辑
   * @param gridItem
   * @param callback
   */
  private copyWidget(gridItem: GridItemDataInterface, callback?: Function) {
    const {
      widgetId,
      x,
      y,
      w,
      h,
      isLoading,
      showChart,
      uniqueIDFromMyComponent,
    } = gridItem
    const chartType = Array.isArray(gridItem.chartType)
      ? gridItem.chartType[1]
      : gridItem.chartType
    if (!widgetId) {
      this.$message.error('当前组件内容为空，无法复制')
      if (callback) {
        callback()
      }
      return
    }
    const parameters = {
      projectId: +this.projectId,
      dashboardId: this.dashBoardId,
      id: widgetId,
      x,
      y: +y + 1,
    }

    widgetCopied({
      data: {
        ...parameters,
      },
    })
      .then((response: any) => {
        const { result, code } = response?.data
        if (code === 100) {
          result?.forEach((item: any) => {
            const { data, id } = item
            const { parentId } = data?.chartOptions
            const widget = {
              ...data,
              widgetId: id,
              chartOptions: {
                ...data?.chartOptions,
                ...handleInteractionActionObject(data?.chartOptions),
                value: gridItem?.chartOptions.value,
              },
              dataQuery: {
                data: {
                  id: data?.chartOptions.dataId,
                  type: data?.chartOptions.dataType,
                  widgetJson: data?.widgetJson,
                },
              },
              isLoading,
              showChart,
              uniqueIDFromMyComponent,
              moved: false,
            }
            if (parentId === 'root' || parentId === undefined) {
              widget.chartOptions.size = gridItem?.chartOptions.size
              widget.w = w
              widget.h = h
            }
            // table组件，chartOptions中需要columns
            if (chartType === 'table') {
              widget.chartOptions.columns = gridItem?.chartOptions.columns
            }

            if (chartType === 'graphLineChart') {
              widget.chartOptions.subChartOptions.value =
                gridItem.chartOptions.subChartOptions.value
            }
            // 复制成功，将组件添加到画布上
            VisualizationStore.addGridItem(widget)
            parseChartOptionToFunction(widget.chartOptions, widget.chartType)
            // 如果是vistabs的子组件，需要单独获取value
            if (parentId && parentId.indexOf('vistabs-') > -1) {
              this.getWidgetData(
                widget.chartOptions,
                widget as GridItemDataInterface,
                this.gridList.length - 1,
                false
              )
            }
          })
          this.updateActiveGridItem()
          if (callback) {
            callback()
          }
          // 保存画布
          this.onSave()
        } else {
          this.$message.error('复制失败')
        }
      })
      .finally(() => {
        if (callback) {
          callback()
        }
      })
  }

  /**
   * 点击复制组件操作
   * @param gridItem
   */
  onCopy(gridItem: GridItemDataInterface, callback?: Function) {
    const { widgetId, interactionJson } = gridItem
    if (!widgetId) {
      this.$message.error('当前组件内容为空，无法复制')
      if (callback) {
        callback()
      }
      return
    }
    // 如果存在关联图表，进行提示
    const interactionExist = interactionJson?.relateWidgets?.some(
      (relateWidget: any) => relateWidget?.status === 'connected'
    )
    if (interactionExist) {
      this.$confirm({
        title: '存在关联图表，确认要复制？',
        content:
          '该组件复制包括关联关系的复制，被关联图表会以多个相关组件的交集作为过滤条件进行渲染，是否继续复制？',
        okText: '确定',
        cancelText: '取消',
        onOk: () => {
          this.copyWidget(gridItem, callback)
        },
        onCancel: () => {
          if (callback) {
            callback()
          }
        },
      })
    } else {
      this.copyWidget(gridItem, callback)
    }
  }
}
</script>
<style lang="less" scoped>
@import '@/constants';

.grid-layout-design.report-mode {
  min-height: auto !important; // 报告模式直接根据内部高度，无需外层设置minheight
  padding: 0 72px;

  .btn-del {
    cursor: pointer;
    font-size: 18px;
    position: absolute;
    right: 45px;
  }

  .lock-mode {
    cursor: not-allowed;
    opacity: 0.45;
    user-select: none;

    /deep/ .ant-btn {
      cursor: not-allowed;
    }
  }
}

.grid-layout-design {
  height: 100%;
  position: relative;
  width: 100%;

  .line {
    position: absolute;

    &.ht,
    &.hc,
    &.hb {
      background: #e6505a;
      height: 1px;
      left: 0;
      position: absolute;
      top: 50%;
      width: 100%;
      z-index: 10000;
    }

    &.vl,
    &.vc,
    &.vr {
      background: #e6505a;
      height: 100%;
      left: 50%;
      position: absolute;
      top: 0;
      width: 1px;
      z-index: 10000;
    }
  }

  /deep/ .formula-style {
    background-color: #e1e3fe;
    color: #6973ff;
  }

  /deep/ .result-style {
    color: #6973ff;
  }
}
</style>
